use std::thread;
use std::fs::File;
use std::time::Duration;
use std::process::Command;

use image::AnimationDecoder;
use image::codecs::gif::GifDecoder;
use image::{Pixel, Rgba, RgbaImage};
use image::io::Reader as ImageReader;
use image::buffer::EnumeratePixelsMut;
use image::imageops::{
    resize, FilterType, invert, contrast,
};

use clap::Parser;

#[derive(Parser)]
#[clap(author, version, about, long_about = None)]
struct Args {
   
    /// Name of target file that will be text-arted
    file_name: String,

    /// Milliseconds of pause between gif frames.
    /// Only in 'gif' mode.
    #[clap(short, long, default_value_t = 100)]
    duration: u64, 
    
    /// Image scale of symbols.
    #[clap(short, long, default_value_t = 200)]
    scale: u32,
    
    /// Working mode. 'image' provide converting only 1 frame.
    /// 'gif' provide frame by frame convertation.
    #[clap(short = 'm', long, default_value = "image")]
    convert_mode: String,
    
    /// Convert inverted image.
    #[clap(short, long)]
    invert: bool,
    
    /// Convert extra-contrasted image.
    #[clap(short, long)]
    contrast: bool,

}

fn main() {
        
    let args = Args::parse();

    if args.convert_mode == "gif" {

        let gif_file = File::open(args.file_name).expect("FileOpenError");
        let decoder = GifDecoder::new(gif_file).expect("GifDecodError");
        let frames = decoder.into_frames();
        let frames = frames.collect_frames().expect("GifFramingError");

        let mut text_art_frames: Vec<String> = vec![];
        for frame in frames {
            let imgbuf = frame.into_buffer();
            let text_art = text_art_from(
                &imgbuf, args.scale, args.invert, args.contrast,
            );

            text_art_frames.push(text_art);
        }

        loop {
            for frame in &text_art_frames {
                thread::sleep(Duration::from_millis(args.duration));
                Command::new("clear").status().expect("ClearError");
                println!("{frame}");
            }
        }

    } else if args.convert_mode == "image" {

        let img = ImageReader::open(args.file_name)
            .expect("ImageReadError")
            .decode().expect("ImageDecodeError")
            .into_rgba8();    

        let text_art = text_art_from(
            &img, args.scale, args.invert, args.contrast,
        );

        println!("{text_art}");

    } else {
        panic!("Undefined MODE! Use 'image' or 'gif'.")
    }
}

fn text_art_from(img: &RgbaImage, scale: u32, inverted: bool, contrasted: bool) -> String {
        
    let mut text_art = String::new();

    let mut imgbuf = resize(img, scale, scale/2, FilterType::Nearest);
    
    if inverted { invert(&mut imgbuf); }
    if contrasted { contrast(&mut imgbuf, 1000.0); }

    for (_, mut pixels) in imgbuf.enumerate_rows_mut() {
       
        let texted_row = row_art(&mut pixels);

        text_art.push_str(&texted_row);
        text_art.push('\n');
    }
    
    text_art
}

fn light_index_from(pixel: &Rgba<u8>) -> u8 {

    let channels = pixel.channels();

    let light_index: u16 = channels[..3].iter()
        .map(|x| *x as u16)
        .sum::<u16>();

    let light_index = light_index / 3;
    let light_index: u8 = light_index as u8;

    light_index 
}

fn row_art(pixels: &mut EnumeratePixelsMut<Rgba<u8>>) -> String {
    
    let mut art = String::new(); 
    
    for (_, _, pixel) in pixels.into_iter() {
        
        let light_index = light_index_from(&pixel);
        
        art.push(
            match light_index {
                0..=15    => '@', 16..=30   => '#',
                31..=45   => 'B', 46..=60   => 'G',
                61..=75   => '&', 76..=90   => 'P',
                91..=105  => '5', 106..=120 => 'Y',
                121..=135 => 'J', 136..=150 => '7',
                151..=165 => '?', 166..=180 => '!',
                181..=195 => '~', 196..=210 => '^',
                211..=225 => ':', 226..=240 => '.',
                241..=255 => ' ',
            }
        );
    }

    art
}

